﻿namespace System.Data.Entity
{
    using Collections.Generic;
    using Linq;
    using Linq.Expressions;

    public class DbContext<TId> : DbContext, IDbContext<TId>
    {
        public virtual IQueryable<TEntity> All<TEntity>(params Expression<Func<TEntity, object>>[] includeProperties) where TEntity : class, IEntity<TId>
        {
            return includeProperties.Aggregate<Expression<Func<TEntity, object>>, IQueryable<TEntity>>(Set<TEntity>(), (current, includeProperty) => current.Include(includeProperty));
        }

        public virtual TEntity Find<TEntity>(TId id) where TEntity : class, IEntity<TId>
        {
            return Set<TEntity>().Find(id);
        }

        public virtual TEntity Create<TEntity>() where TEntity : class, IEntity<TId>
        {
            var entity = Set<TEntity>().Create();
            entity.GetType().GetProperties().ToList().ForEach(pi =>
            {
                if (pi.PropertyType.IsGenericType &&
                    pi.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) &&
                    pi.PropertyType.GetGenericArguments().Count() == 1 &&
                    pi.PropertyType.GetGenericArguments()[0].GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntity<>)))
                {
                    var navigateModel = pi.PropertyType.GetGenericArguments()[0];
                    pi.SetValue(entity, Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(navigateModel)), null);
                }
            });
            return entity;
        }

        public virtual TId Insert<TEntity>(TEntity entity) where TEntity : class, IEntity<TId>
        {
            Set<TEntity>().Add(entity);
            return entity.Id;
        }

        public virtual void Update<TEntity>(TEntity entity) where TEntity : class, IEntity<TId>
        {
            Entry(entity).State = EntityState.Modified;
        }

        public virtual void Delete<TEntity>(TId id) where TEntity : class, IEntity<TId>
        {
            Set<TEntity>().Remove(Set<TEntity>().Find(id));
        }
    }
}
